package com.gitee.wsl.compose.chart.geometry.model.cliper

import com.gitee.wsl.compose.chart.geometry.model.GeometryBuffer

/*
 * Copyright 2012, 2013 Hannes Janetzek
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */


/**
 * from http://en.wikipedia.org/wiki/Cohen%E2%80%93
 * Sutherland_algorithm
 */
class LineClipper(
    private var xmin: Float,
    private var ymin: Float,
    private var xmax: Float,
    private var ymax: Float
) {
    fun setRect(minx: Float, miny: Float, maxx: Float, maxy: Float) {
        xmin = minx
        ymin = miny
        xmax = maxx
        ymax = maxy
    }

    private var mPrevOutcode = 0
    private var mPrevX = 0f
    private var mPrevY = 0f
    var outX1 = 0f
    var outY1 = 0f
    var outX2 = 0f
    var outY2 = 0f
    fun clipStart(x0: Float, y0: Float): Boolean {
        mPrevX = x0
        mPrevY = y0
        mPrevOutcode = INSIDE
        if (x0 < xmin) mPrevOutcode = mPrevOutcode or LEFT else if (x0 > xmax) mPrevOutcode =
            mPrevOutcode or RIGHT
        if (y0 < ymin) mPrevOutcode = mPrevOutcode or BOTTOM else if (y0 > ymax) mPrevOutcode =
            mPrevOutcode or TOP
        return mPrevOutcode == INSIDE
    }

    fun outcode(x: Float, y: Float): Int {
        var outcode = INSIDE
        if (x < xmin) outcode = outcode or LEFT else if (x > xmax) outcode = outcode or RIGHT
        if (y < ymin) outcode = outcode or BOTTOM else if (y > ymax) outcode = outcode or TOP
        return outcode
    }

    /**
     * @return 0 if not intersection, 1 fully within, -1 clipped (and 'out' set
     * to new points)
     */
    fun clipNext(x1: Float, y1: Float): Int {
        val accept: Int
        var outcode = INSIDE
        if (x1 < xmin) outcode = outcode or LEFT else if (x1 > xmax) outcode = outcode or RIGHT
        if (y1 < ymin) outcode = outcode or BOTTOM else if (y1 > ymax) outcode = outcode or TOP
        accept = if (mPrevOutcode or outcode == 0) {
            // Bitwise OR is 0. Trivially accept
            1
        } else if (mPrevOutcode and outcode != 0) {
            // Bitwise AND is not 0. Trivially reject
            0
        } else {
            if (clip(mPrevX, mPrevY, x1, y1, mPrevOutcode, outcode)) -1 else 0
        }
        mPrevOutcode = outcode
        mPrevX = x1
        mPrevY = y1
        return accept
    }

    fun clipSegment(x1: Float, y1: Float, x2: Float, y2: Float): Int {
        clipStart(x1, y1)
        return clipNext(x2, y2)
    }

    /* CohenSutherland clipping algorithm clips a line from
	 * P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
	 * diagonal from (xmin, ymin) to (xmax, ymax).
	 * based on en.wikipedia.org/wiki/Cohen-Sutherland */
    private fun clip(
        x0: Float,
        y0: Float,
        x1: Float,
        y1: Float,
        outcode0: Int,
        outcode1: Int
    ): Boolean {
        var x0 = x0
        var y0 = y0
        var x1 = x1
        var y1 = y1
        var outcode0 = outcode0
        var outcode1 = outcode1
        var accept = false
        while (true) {
            if (outcode0 or outcode1 == 0) {
                /* Bitwise OR is 0. Trivially accept and get out of loop */
                accept = true
                break
            } else if (outcode0 and outcode1 != 0) {
                /* Bitwise AND is not 0. Trivially reject and get out of loop */
                break
            } else {
                /* failed both tests, so calculate the line segment to clip
				 * from an outside point to an intersection with clip edge */
                var x = 0f
                var y = 0f

                /* At least one endpoint is outside the clip rectangle; pick it. */
                val outcodeOut = if (outcode0 == 0) outcode1 else outcode0
                /* Now find the intersection point;
				 * use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope)
				 * * (y - y0) */if (outcodeOut and TOP != 0) {
                    /* point is above the clip rectangle */
                    x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0)
                    y = ymax
                } else if (outcodeOut and BOTTOM != 0) {
                    /* point is below the clip rectangle */
                    x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0)
                    y = ymin
                } else if (outcodeOut and RIGHT != 0) {
                    /* point is to the right of clip rectangle */
                    y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0)
                    x = xmax
                } else if (outcodeOut and LEFT != 0) {
                    /* point is to the left of clip rectangle */
                    y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0)
                    x = xmin
                }
                var outcode = INSIDE
                if (x < xmin) outcode = outcode or LEFT else if (x > xmax) outcode =
                    outcode or RIGHT
                if (y < ymin) outcode = outcode or BOTTOM else if (y > ymax) outcode =
                    outcode or TOP

                /* Now we move outside point to intersection point to clip
				 * and get ready for next pass. */if (outcodeOut == outcode0) {
                    x0 = x
                    y0 = y
                    outcode0 = outcode
                } else {
                    x1 = x
                    y1 = y
                    outcode1 = outcode
                }
            }
        }
        if (accept) {
            outX1 = x0
            outY1 = y0
            outX2 = x1
            outY2 = y1
        }
        return accept
    }

    fun getLine(out: FloatArray?, offset: Int): FloatArray {
        if (out == null) return floatArrayOf(outX1, outY1, outX2, outY2)
        out[offset + 0] = outX1
        out[offset + 1] = outY1
        out[offset + 2] = outX2
        out[offset + 3] = outY2
        return out
    }

    fun clipLine(`in`: GeometryBuffer, out: GeometryBuffer): Int {
        out.clear()
        var pointPos = 0
        var numLines = 0
        var i = 0
        val n = `in`.index.size
        while (i < n) {
            val len = `in`.index[i]
            if (len < 0) break
            if (len < 4) {
                pointPos += len
                i++
                continue
            }
            if (len == 0) {
                i++
                continue
            }
            var inPos = pointPos
            val end = inPos + len
            var x = `in`.points[inPos++]
            var y = `in`.points[inPos++]
            var inside = clipStart(x, y)
            if (inside) {
                out.startLine()
                out.addPoint(x, y)
                numLines++
            }
            while (inPos < end) {
                /* get the current way point coordinates */
                x = `in`.points[inPos++]
                y = `in`.points[inPos++]
                val clip = clipNext(x, y)
                if (clip == 0) {
                    /* current segment is fully outside */
                    inside = false // needed?
                } else if (clip == 1) {
                    /* current segment is fully within */
                    out.addPoint(x, y)
                } else { /* clip == -1 */
                    if (inside) {
                        /* previous was inside */
                        out.addPoint(outX2, outY2)
                    } else {
                        /* previous was outside */
                        out.startLine()
                        numLines++
                        out.addPoint(outX1, outY1)
                        out.addPoint(outX2, outY2)
                    }
                    inside = clipStart(x, y)
                }
            }
            pointPos = end
            i++
        }
        return numLines
    }

    companion object {
        const val INSIDE = 0 // 0000
        const val LEFT = 1 // 0001
        const val RIGHT = 2 // 0010
        const val BOTTOM = 4 // 0100
        const val TOP = 8 // 1000
    }
}

